import { recorderMachine } from "./weila_record_fsm";
import {RECORDER_INTERFACE} from "./recorder_interface";
import { createActor, fromPromise } from "xstate";
import { WebMRecorder } from "./webm_recorder";
import { WorkletRecorder } from "./worklet_recorder";
import { ScriptRecorder } from "./script_recorder";
import { WL_PromiseCallback } from "sdk/store/weila_datas";

class WeilaRecorder {
    private actorMachine: any;
    private scope: any | null;
    private sampleRate: number;
    private micStream: any | null;
    private recorder: RECORDER_INTERFACE | null;
    private dataReceiveCallback?: (data: Int16Array) => void;

    constructor(private audioContext: any) {
        const m = recorderMachine.provide({
            actions: {
                recorder_stopped_entry: this.recorder_stopped_entry.bind(this),
                recorder_started_entry: this.recorder_started_entry.bind(this),
                start_record_done: this.start_record_done.bind(this),
                start_record_error: this.start_record_error.bind(this),
                stop_record_done: this.stop_record_done.bind(this),
                stop_record_error: this.stop_record_error.bind(this),
            },
            actors: {
                start_record: fromPromise(this.start_record.bind(this)),
                stop_record: fromPromise(this.stop_record.bind(this)),
            }
        })

        this.actorMachine = createActor(m);
        this.actorMachine.subscribe((state: { value: any; context: any; }) => {
            console.log("WeilaRecorder state", state.value, state.context);
        })

        this.actorMachine.start();

        this.sampleRate = this.audioContext.sampleRate;
        this.scope = navigator.mediaDevices || {};
        if (!this.scope.getUserMedia) {
            this.scope = navigator;
            // 判断到getUserMedia有定义，则不作为，否则赋值有效的
            this.scope.getUserMedia ||
            (this.scope.getUserMedia = this.scope.webkitGetUserMedia ||
                this.scope.mozGetUserMedia || this.scope.msGetUserMedia);
        }
        this.recorder = null;
        this.micStream = null;        
    }

    private recorder_stopped_entry(args: {context: any, event: any}) {
        console.log('recorder_stopped_entry', args);
    }

    private recorder_started_entry(args: {context: any, event: any}) {
        console.log('recorder_started_entry', args);
    }

    private start_record_done(args: {context: any, event: any}) {
        console.log('start_record_done', args);
        const callback = args.event.output.param;
        if (callback && callback.resolve) {
            callback.resolve(args.event.output.result);
        }
    }

    private start_record_error(args: {context: any, event: any}) {
        console.log('start_record_error', args);
        const callback = args.event.error.param;
        if (callback && callback.reject) {
            callback.reject(args.event.error.error);
        }
    }

    private stop_record_done(args: {context: any, event: any}) {
        console.log('stop_record_done', args);
        const callback = args.event.output.param;
        if (callback && callback.resolve) {
            callback.resolve(args.event.output.result);
        }
    }

    private stop_record_error(args: {context: any, event: any}) {
        console.log('stop_record_error', args);
        const callback = args.event.error.param;
        if (callback && callback.reject) {
            callback.reject(args.event.error.error);
        }
    }

    private on_receive_pcm_data(f32Arr: Float32Array) {
        const i16Arr = new Int16Array(f32Arr.map(v => {
            return Math.ceil(v * 32768);
        }));

        if (this.dataReceiveCallback) {
            this.dataReceiveCallback(i16Arr);
        }
    }

    private async tryConnectByWebM(): Promise<boolean> {
        const webM = new WebMRecorder(this.micStream, this.audioContext.sampleRate);
        if (webM.isSupport()) {
            webM.onError(async (e: any) => {
                console.log("WEBM 录音器出错", e);
                try {
                    await this.tryConnectByWorklet();
                }catch (e) {
                    try {
                        await this.tryConnectByScript();
                    }catch (e2) {
                        //TODO: 通知事件发生
                        console.log("出错了", e2);
                    }
                }
            });

            webM.onDataReceive(this.on_receive_pcm_data.bind(this));
            await webM.connect();
            this.recorder = webM;
            console.log("WEBM 录音器已连接");
            return true;
        }else {
            console.log("WEBM 录音器不支持");
        }

        return Promise.reject("录音器不支持");
    }

    private async tryConnectByWorklet(): Promise<boolean> {
        console.log("尝试连接 worklet 录音器");
        const worklet = new WorkletRecorder(this.audioContext, this.micStream);
        if (worklet.isSupport()) {
            worklet.onDataReceive(this.on_receive_pcm_data.bind(this));
            await worklet.connect();
            this.recorder = worklet;
            console.log("WORKLET 录音器已连接");
            return true;
        }

        return Promise.reject("录音器不支持");
    }

    private async tryConnectByScript(): Promise<boolean> {
        console.log("尝试连接 script 录音器");
        const script = new ScriptRecorder(this.audioContext, this.micStream);
        if (script.isSupport()) {
            script.onDataReceive(this.on_receive_pcm_data.bind(this));
            await script.connect();
            this.recorder = script;
            console.log("SCRIPT 录音器已连接");
            return true;
        }else {
            console.log("SCRIPT 录音器不支持");
        }

        return Promise.reject("录音器不支持");
    }

    private async start_record(params: any) {
        console.log('start_record', params);
        try {
            await new Promise((resolve, reject) => {
                const succ = (value: any) => {
                    console.log("获取mic成功")
                    setTimeout(async () => {
                        this.micStream = value;
                        if (this.is_stream_open()) {
                            try {
                                await this.tryConnectByWebM();
                                console.log("连接WebM")
                            }catch (e) {
                                console.log("连接WebM失败,尝试Worklet", e);
                                try {
                                    await this.tryConnectByWorklet();
                                }catch (e2) {
                                    console.log("连接Worklet失败,尝试Script", e2);
                                    try {
                                        await this.tryConnectByScript();
                                    }catch (e3) {
                                        reject(new Error("录音器均不支持"));
                                        return;
                                    }
                                }
                            }

                            resolve(true);
                        }else {
                            reject(new Error("获取mic失败"));
                        }
                    }, 100);
                };


                try {
                    const promise = this.scope.getUserMedia({
                        audio: true
                    })
                    promise.then(succ).catch((e: any) => {
                        reject(new Error("获取mic失败"));
                    })
                }catch (e) {
                    this.scope.getUserMedia({
                        audio: {
                            noiseSuppression: false,
                            echoCancellation: false,
                            autoGainControl: true,
                            sampleRate: this.sampleRate
                        }
                    }, succ, (e: any) => {
                        reject(new Error("获取mic失败"));
                    })
                }
            })
        }catch (e) {
            console.log(e);
            return Promise.reject({error: e, ...params.input});
        }

        try {
            if (this.recorder) {
                await this.recorder.start();
                return {result: true, ...params.input}
            }else {
                return Promise.reject({error: "录音器不支持", ...params.input});
            }
        }catch (e) {
            console.log("start error", e);


            return Promise.reject({error: e, ...params.input});
        }
    }

    private async stop_record(params: any) {
        console.log('stop_record', params);
        if (this.recorder) {
            try {
                await this.recorder.stop();
            }catch (e) {
                console.log("stop error", e);
            }
            
            try {
                await this.recorder.disconnect();
                return {result: true, ...params.input}
            }catch (e) {
                console.log("disconnect error", e);
                return Promise.reject({error: e, ...params.input});
            }finally {
                this.recorder = null;
                if (this.micStream) {
                    let tracks = this.micStream.getTracks && this.micStream.getTracks() || this.micStream.audioTracks;
                    for (let i = 0; i < tracks.length; i++) {
                        let track = tracks[0];
                        track.stop && track.stop();
                    }
                    this.micStream = null;
                }
            }
        }

        return Promise.reject({error: "录音器不支持", ...params.input});
    }    

    private is_stream_open() {
        if (this.micStream) {
            const tracks = this.micStream.getTracks && this.micStream.getTracks() || this.micStream.audioTracks || [];
            const track = tracks[0];
            if (track) {
                const state = track.readyState;
                return state == "live" || state == track.LIVE;
            }
        }

        return false
    }


    public weilaRecorder_start(): Promise<boolean> {
        return new Promise<boolean>((resolve, reject) => {
            const callback = {} as WL_PromiseCallback;
            callback.resolve = resolve;
            callback.reject = reject;

            this.actorMachine.send({type: "start_record", param: {...callback}});
        })
    }

    public weilaRecorder_stop(): Promise<boolean> {
        return new Promise<boolean>((resolve, reject) => {
            const callback = {} as WL_PromiseCallback;
            callback.resolve = resolve;
            callback.reject = reject;
            this.actorMachine.send({type: "stop_record", param: {...callback}});
        })
    }

    public weilaRecorder_setDataReceiveCallback(callback: (data: Int16Array) => void) {
        this.dataReceiveCallback = callback;
    }
}

export {WeilaRecorder}